use std::env::{remove_var, set_var, var, VarError};
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::fs::read_to_string;
use std::fs::File;
use std::io;
use std::io::Read;
use std::num::ParseIntError;

use crate::gen_title;

pub fn ch4_18_1() {
    gen_title("ch4_18", 18);

    let s1 = Some("some1");
    let s2 = Some("some2");
    let n: Option<&str> = None;

    let o1: Result<&str, &str> = Ok("ok1");
    let o2: Result<&str, &str> = Ok("ok2");
    let e1: Result<&str, &str> = Err("error1");
    let e2: Result<&str, &str> = Err("error2");

    assert_eq!(s1.or(s2), s1);
    println!("s1.or(s2) = {:?}", s1.or(s2));

    assert_eq!(s1.or(n), s1);
    println!("s1.or(n) = {:?}", s1.or(n));

    assert_eq!(n.or(s1), s1);
    println!("n.or(s1) = {:?}", n.or(s1));

    assert_eq!(n.or(n), n);
    println!("n.or(n) = {:?}", n.or(n));

    assert_eq!(o1.or(o2), o1);
    println!("o1.or(o2) = {:?}", o1.or(o2));

    assert_eq!(o1.or(e1), o1);
    println!("o1.or(e1) = {:?}", o1.or(e1));

    assert_eq!(e1.or(e2), e2);
    println!("e1.or(e2) = {:?}", e1.or(e2));

    assert_eq!(s1.and(s2), s2);
    println!("s1.and(s2) = {:?}", s1.and(s2));

    assert_eq!(s1.and(n), n);
    println!("s1.and(n) = {:?}", s1.and(n));

    assert_eq!(n.and(s1), n);
    println!("n.and(s1) = {:?}", n.and(s1));

    assert_eq!(n.and(n), n);
    println!("n.and(n) = {:?}", n.and(n));

    assert_eq!(o1.and(o2), o2);
    println!("o1.and(o2) = {:?}", o1.and(o2));

    assert_eq!(o1.and(e1), e1);
    println!("o1.and(e1) = {:?}", o1.and(e1));

    assert_eq!(e1.and(o1), e1);
    println!("e1.and(o1) = {:?}", e1.and(o1));

    assert_eq!(e1.and(e2), e1);
    println!("e1.and(e2) = {:?}", e1.and(e2));
}

pub fn ch4_18_2() {
    gen_title("ch4_18", 18);

    let s1 = Some("some1");
    let s2 = Some("some2");
    let fn_some = || Some("some2");

    let n: Option<&str> = None;
    let fn_none = || None;

    assert_eq!(s1.or_else(fn_some), s1);
    println!("s1.or_else(fn_some) = {:?}", s1.or_else(fn_some));

    assert_eq!(s1.or_else(fn_none), s1);
    println!("s1.or_else(fn_none) = {:?}", s1.or_else(fn_none));

    assert_eq!(n.or_else(fn_some), s2);
    println!("n.or_else(fn_some) = {:?}", n.or_else(fn_some));

    assert_eq!(n.or_else(fn_none), None);
    println!("n.or_else(fn_none) = {:?}", n.or_else(fn_none));

    let o1: Result<&str, &str> = Ok("ok1");
    let o2: Result<&str, &str> = Ok("ok2");
    let fn_ok = |_| Ok("ok2");

    let e1: Result<&str, &str> = Err("error1");
    let e2: Result<&str, &str> = Err("error2");
    let fn_err = |_| Err("error2");

    assert_eq!(o1.or_else(fn_ok), o1);
    println!("o1.or_else(fn_ok) = {:?}", o1.or_else(fn_ok));

    assert_eq!(o1.or_else(fn_err), o1);
    println!("o1.or_else(fn_err) = {:?}", o1.or_else(fn_err));

    assert_eq!(e1.or_else(fn_ok), o2);
    println!("e1.or_else(fn_ok) = {:?}", e1.or_else(fn_ok));

    assert_eq!(e1.or_else(fn_err), e2);
    println!("e1.or_else(fn_err) = {:?}", e1.or_else(fn_err));
}

pub fn ch4_18_3() {
    gen_title("ch4_18_3", 18);

    let s1 = Some("some1");
    let s2 = Some("some2");
    let fn_some = |_| Some("some2");

    let n: Option<&str> = None;
    let fn_none = |_| None;

    assert_eq!(s1.and_then(fn_some), s2);
    println!("s1.and_then(fn_some) = {:?}", s1.and_then(fn_some));

    assert_eq!(s1.and_then(fn_none), n);
    println!("s1.and_then(fn_none) = {:?}", s1.and_then(fn_none));

    assert_eq!(n.and_then(fn_some), n);
    println!("n.and_then(fn_some) = {:?}", n.and_then(fn_some));

    assert_eq!(n.and_then(fn_none), n);
    println!("n.and_then(fn_none) = {:?}", n.and_then(fn_none));

    let o1: Result<&str, &str> = Ok("ok1");
    let o2: Result<&str, &str> = Ok("ok2");
    let fn_ok = |_| Ok("ok2");

    let e1: Result<&str, &str> = Err("error1");
    let e2: Result<&str, &str> = Err("error2");
    let fn_err = |_| Err("error2");

    assert_eq!(o1.and_then(fn_ok), o2);
    println!("o1.and_then(fn_ok) = {:?}", o1.and_then(fn_ok));

    assert_eq!(o1.and_then(fn_err), e2);
    println!("o1.and_then(fn_err) = {:?}", o1.and_then(fn_err));

    assert_eq!(e1.and_then(fn_ok), e1);
    println!("e1.and_then(fn_ok) = {:?}", e1.and_then(fn_ok));

    assert_eq!(e1.and_then(fn_err), e1);
    println!("e1.and_then(fn_err) = {:?}", e1.and_then(fn_err));
}

pub fn ch4_18_4() {
    gen_title("ch4_18_4", 18);

    let s1 = Some(3);
    let s2 = Some(6);
    let n = None;

    let fn_is_even = |x: &i8| x % 2 == 0;

    assert_eq!(s1.filter(fn_is_even), n);
    println!("s1.filter(fn_is_even) = {:?}", s1.filter(fn_is_even));

    assert_eq!(s2.filter(fn_is_even), s2);
    println!("s2.filter(fn_is_even) = {:?}", s2.filter(fn_is_even));

    assert_eq!(n.filter(fn_is_even), n);
    println!("n.filter(fn_is_even) = {:?}", n.filter(fn_is_even));
}

pub fn ch4_18_5() {
    gen_title("ch4_18_5", 18);

    let s1 = Some("abcde");
    let s2 = Some(5);

    let n1: Option<&str> = None;
    let n2: Option<usize> = None;

    let o1: Result<&str, &str> = Ok("abcde");
    let o2: Result<usize, &str> = Ok(5);

    let e1: Result<&str, &str> = Err("abcde");
    let e2: Result<usize, &str> = Err("abcde");

    let fn_character_count = |s: &str| s.chars().count();

    assert_eq!(s1.map(fn_character_count), s2);
    println!(
        "s1.map(fn_character_count) = {:?}",
        s1.map(fn_character_count)
    );

    assert_eq!(n1.map(fn_character_count), n2);
    println!(
        "n1.map(fn_character_count) = {:?}",
        n1.map(fn_character_count)
    );

    assert_eq!(o1.map(fn_character_count), o2);
    println!(
        "o1.map(fn_character_count) = {:?}",
        o1.map(fn_character_count)
    );

    assert_eq!(e1.map(fn_character_count), e2);
    println!(
        "e1.map(fn_character_count) = {:?}",
        e1.map(fn_character_count)
    );
}

pub fn ch4_18_6() {
    gen_title("ch4_18_6", 18);

    let o1: Result<&str, &str> = Ok("abcde");
    let o2: Result<&str, isize> = Ok("abcde");

    let e1: Result<&str, &str> = Err("404");
    let e2: Result<&str, isize> = Err(404);

    let fn_character_count = |s: &str| -> isize { s.parse().unwrap() };

    assert_eq!(o1.map_err(fn_character_count), o2);
    println!(
        "o1.map_err(fn_character_count) = {:?}",
        o1.map_err(fn_character_count)
    );

    assert_eq!(e1.map_err(fn_character_count), e2);
    println!(
        "e1.map_err(fn_character_count) = {:?}",
        e1.map_err(fn_character_count)
    );
}

pub fn ch4_18_7() {
    gen_title("ch4_18_7", 18);

    const V_DEFAULT: u32 = 1;

    let s: Result<u32, ()> = Ok(10);
    let n: Option<u32> = None;
    let fn_closure = |v: u32| v + 2;

    assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);
    println!(
        "s.map_or(V_DEFAULT, fn_closure) = {:?}",
        s.map_or(V_DEFAULT, fn_closure)
    );

    assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
    println!(
        "n.map_or(V_DEFAULT, fn_closure) = {:?}",
        n.map_or(V_DEFAULT, fn_closure)
    );
}

pub fn ch4_18_8() {
    gen_title("ch4_18_8", 18);

    let s = Some(10);
    let n: Option<i8> = None;

    let fn_closure = |v: i8| v + 2;
    let fn_default = || 1;

    assert_eq!(s.map_or_else(fn_default, fn_closure), 12);
    println!(
        "s.map_or_else(fn_default, fn_closure) = {:?}",
        s.map_or_else(fn_default, fn_closure)
    );

    assert_eq!(n.map_or_else(fn_default, fn_closure), 1);
    println!(
        "n.map_or_else(fn_default, fn_closure) = {:?}",
        n.map_or_else(fn_default, fn_closure)
    );

    let o = Ok(10);
    let e = Err(5);
    let fn_default_for_result = |v: i8| v + 1;

    assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);
    println!(
        "o.map_or_else(fn_default_for_result, fn_closure) = {:?}",
        o.map_or_else(fn_default_for_result, fn_closure)
    );

    assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
    println!(
        "e.map_or_else(fn_default_for_result, fn_closure) = {:?}",
        e.map_or_else(fn_default_for_result, fn_closure)
    );
}

pub fn ch4_18_9() {
    gen_title("ch4_18_9", 18);

    const ERROR_DEFAULT: &str = "error message";

    let s = Some("abcde");
    let n: Option<&str> = None;

    let o: Result<&str, &str> = Ok("abcde");
    let e: Result<&str, &str> = Err(ERROR_DEFAULT);

    assert_eq!(s.ok_or(ERROR_DEFAULT), o);
    println!("s.ok_or(ERROR_DEFAULT) = {:?}", s.ok_or(ERROR_DEFAULT));

    assert_eq!(n.ok_or(ERROR_DEFAULT), e);
    println!("n.ok_or(ERROR_DEFAULT) = {:?}", n.ok_or(ERROR_DEFAULT));
}

pub fn ch4_18_10() {
    gen_title("ch4_18_10", 18);

    let s = Some("abcde");
    let n: Option<&str> = None;
    let fn_err_message = || "error message";

    let o: Result<&str, &str> = Ok("abcde");
    let e: Result<&str, &str> = Err("error message");

    assert_eq!(s.ok_or_else(fn_err_message), o);
    println!(
        "s.ok_or_else(fn_err_message) = {:?}",
        s.ok_or_else(fn_err_message)
    );

    assert_eq!(n.ok_or_else(fn_err_message), e);
    println!(
        "n.ok_or_else(fn_err_message) = {:?}",
        n.ok_or_else(fn_err_message)
    );
}

pub fn ch4_18_11() {
    gen_title("ch4_18_11", 18);

    #[derive(Debug)]
    struct AppError;

    impl Display for AppError {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            write!(f, "An Error Occurred, Please Try Again!")
        }
    }

    fn produce_error() -> Result<(), AppError> {
        Err(AppError)
    }

    match produce_error() {
        Err(e) => eprintln!("{}", e),
        _ => println!("No error"),
    }

    eprintln!("{:?}", produce_error());
}

pub fn ch4_18_12() {
    gen_title("ch4_18_12", 18);

    struct AppError {
        code: usize,
        message: String,
    }

    impl Display for AppError {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            let err_msg = match self.code {
                404 => "Sorry, Can not find the Page!",
                _ => "Sorry, Something is wrong! Please Try Again!",
            };

            write!(f, "{}", err_msg)
        }
    }

    impl Debug for AppError {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "AppError {{ code: {}, message: {} }}",
                self.code, self.message
            )
        }
    }

    fn produce_error() -> Result<(), AppError> {
        Err(AppError {
            code: 404,
            message: String::from("Page not found"),
        })
    }

    match produce_error() {
        Err(e) => eprintln!("{}", e),
        _ => println!("No error"),
    }

    eprintln!("{:?}", produce_error());
    eprintln!("{:#?}", produce_error());
}

pub fn ch4_18_13() {
    gen_title("ch4_18_13", 18);

    #[derive(Debug)]
    struct AppError {
        kind: String,
        message: String,
    }

    impl From<io::Error> for AppError {
        fn from(error: io::Error) -> Self {
            AppError {
                kind: String::from("io"),
                message: error.to_string(),
            }
        }
    }

    fn produce_error() -> Result<(), AppError> {
        let _file = File::open("nonexistent_file.txt")?;

        Ok(())
    }

    println!("{:?}", produce_error());
}

pub fn ch4_18_14() {
    gen_title("ch4_18_14", 18);

    #[derive(Debug)]
    struct AppError {
        kind: String,
        message: String,
    }

    impl From<io::Error> for AppError {
        fn from(error: io::Error) -> Self {
            AppError {
                kind: String::from("io"),
                message: error.to_string(),
            }
        }
    }

    impl From<ParseIntError> for AppError {
        fn from(error: ParseIntError) -> Self {
            AppError {
                kind: String::from("parse"),
                message: error.to_string(),
            }
        }
    }

    fn produce_error() -> Result<(), AppError> {
        let mut file = File::open("resources/poem.txt")?;

        let mut content = String::new();
        file.read_to_string(&mut content)?;

        let _number: usize;
        _number = content.parse()?;

        Ok(())
    }

    println!("{:?}", produce_error());
}

pub fn ch4_18_15() {
    gen_title("ch4_18_15", 18);

    fn render() -> Result<String, Box<dyn Error>> {
        let file = std::env::var("MARKDOWN")?;
        let source = read_to_string(file)?;
        Ok(source)
    }

    fn main_func() -> Result<(), Box<dyn Error>> {
        let html = render()?;
        println!("{}", html);
        Ok(())
    }

    println!("{:?}", main_func());
}

pub fn ch4_18_16() {
    gen_title("ch4_18_16", 18);

    fn render() -> Result<String, MyError> {
        let file = std::env::var("MARKDOWN")?;
        let source = read_to_string(file)?;
        Ok(source)
    }

    fn main_func() -> Result<(), MyError> {
        let html = render()?;
        println!("{}", html);
        Ok(())
    }

    println!("{:?}", main_func());

    #[derive(Debug)]
    enum MyError {
        EnvironmentVariableNotFound,
        IOError(std::io::Error),
    }

    impl From<std::env::VarError> for MyError {
        fn from(_: VarError) -> Self {
            Self::EnvironmentVariableNotFound
        }
    }

    impl From<std::io::Error> for MyError {
        fn from(value: io::Error) -> Self {
            Self::IOError(value)
        }
    }

    impl std::error::Error for MyError {}

    impl std::fmt::Display for MyError {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            match self {
                MyError::EnvironmentVariableNotFound => write!(f, "Environment variable not found"),
                MyError::IOError(err) => write!(f, "IO Error: {}", err.to_string()),
            }
        }
    }
}

pub fn ch4_18_17() {
    gen_title("ch4_18_17", 18);

    fn render() -> Result<String, MyError> {
        let file = std::env::var("MARKDOWN")?;
        let source = read_to_string(file)?;
        Ok(source)
    }

    fn main_func() -> Result<(), MyError> {
        let html = render()?;
        println!("{}", html);
        Ok(())
    }

    println!("{:?}", main_func());

    #[derive(thiserror::Error, Debug)]
    enum MyError {
        #[error("Environment variable not found")]
        EnvironmentVariableNotFound(#[from] std::env::VarError),
        #[error(transparent)]
        IOError(#[from] std::io::Error),
    }
}

pub fn ch4_18_18() {
    gen_title("ch4_18_18", 18);

    error_chain::error_chain! {
      foreign_links {
        EnvironmentVariableNotFound(::std::env::VarError);
        IOError(::std::io::Error);
      }
    }

    fn render() -> Result<String> {
        set_var("MARKDOWN", "resources/poem.txt");
        let file = var("MARKDOWN")?;
        let source = read_to_string(file)?;
        Ok(source)
    }

    fn main_func() -> Result<()> {
        let html = render()?;
        println!("{}", html);
        Ok(())
    }

    println!("{:?}", main_func());
}

pub fn ch4_18_19() {
    gen_title("ch4_18_19", 18);

    fn render() -> anyhow::Result<String> {
        remove_var("MARKDOWN");
        let file = var("MARKDOWN")?;
        let source = read_to_string(file)?;
        Ok(source)
    }

    fn main_func() -> anyhow::Result<()> {
        let html = render()?;
        println!("{}", html);
        Ok(())
    }

    println!("{:?}", main_func());
}
